#region Using Statements
using System;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using KLib.Physics;
using KLib.Physics.Collisions;

using System.Reflection;
#endregion

namespace KLib.Entities
{
    /// <summary>
    /// Entites are any in-game objects.
    /// </summary>
    public class Entity : IPositionable, IRotatable, IColorable, IDrawable, INameable, IDisposable, IDestroyable, IBoundable
    {
        #region Fields

        private Texture2D texture;
        private Vector2 scale;

        private RigidBody body;

        private Color color = Color.White;

        private bool visible = true;
        private string name = String.Empty;

        private string textureName = String.Empty;
        #endregion

        #region Properties
        [ContentSerializerIgnore()]
        public Texture2D Texture
        {
            get { return texture; }
            //Probably not a good idea to fully expose it, I left it like this for testing at 6:00am after no sleep =D
            set 
            { 
                texture = value;
            }
        }

        [ContentSerializer()]
        public string TextureName
        {
            get { return textureName; }
            set 
            {
                if (texture == null)
                    textureName = value;
                else
                    textureName = texture.Name;
            }
        }
        [ContentSerializer()]
        public Vector2 Position
        {
            get { return body.Position; }
            set { body.Position = value; }
        }
        [ContentSerializer()]
        public Vector2 Scale
        {
            get { return scale; }
            set { scale = value; }
        }

        [ContentSerializer(SharedResource = true)]
        public float Rotation
        {
            get { return body.Rotation; }
            set { body.Rotation = value; }
        }

        [ContentSerializer()]
        public RigidBody PhysicsBody
        {
            get { return body; }
            set { body = value; }
        }
        [ContentSerializer()]
        public Color Color
        {
            get { return color; }
            set { color = value; }
        }

        [ContentSerializer()]
        public bool Enabled
        {
            get { return body.Enabled; }
            set { body.Enabled = value; }
        }
        [ContentSerializer()]
        public bool Visible
        {
            get { return visible; }
            set { visible = value; }
        }
        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore()]
        public AABB Bounds
        {
            get { return body.Bounds; }
        }
        [ContentSerializer()]
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        #endregion

        #region Initialization

        /// <summary>
        /// Creates a new, blank Entity;
        /// </summary>
        public Entity()
        {
            body = new RigidBody();
        }

        /// <summary>
        /// Creates a new, blank Entity with a name. The name is used completely for developers, end-users never see it.
        /// </summary>
        /// <param name="name"></param>
        public Entity(string name)
        {
            body = new RigidBody();
            this.name = name;
        }

        /// <summary>
        /// Creates a new Entity with the same values as another entity
        /// </summary>
        /// <param name="entity">The entity to be copied</param>
        public Entity(Entity entity)
        {
            this.Texture = entity.Texture;
            this.PhysicsBody = new RigidBody(entity.PhysicsBody);
            this.Color = entity.Color;
        }

        /// <summary>
        /// Creates a new Entity with the same values as another entity with a name. The name is used completely for developers, end-users never see it.
        /// </summary>
        /// <param name="entity">The entity to be copied</param>
        public Entity(Entity entity, string name)
        {
            this.Texture = entity.Texture;
            this.PhysicsBody = new RigidBody(entity.PhysicsBody);
            this.Color = entity.Color;
            this.name = name;
        }
        #endregion

        #region Methods

        public void Update(GameTime gameTime)
        {
            body.Update();
        }

        #region IPositionable Methods
        public void Move(Vector2 amount)
        {
            body.Move(amount);
        }

        public void MoveTo(Vector2 position)
        {
            body.MoveTo(position);
        }
        #endregion

        #region IRotatable Methods
        public void Rotate(float radians)
        {
            body.Rotate(radians);
        }

        public void RotateTo(float radians)
        {
            body.RotateTo(radians);
        }
        #endregion

        #region IDrawable Method
        public void Draw(KLib.Graphics.SpriteRenderer spriteBatch)
        {
            spriteBatch.Draw(this);
        }
        #endregion


        #region ITexturable Methods
        public void SetTexture(ref Texture2D texture)
        {
            this.Texture = texture;
        }
        #endregion

        #endregion

        #region Helper Methods

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            body.Dispose();
        }

        #endregion

        #region IDestroyable Members

        private bool destroyed = false;
        public bool Destroyed
        {
            get { return destroyed; }
            set { destroyed = value; }
        }

        public void Destroy()
        {
            body.Dispose();
            destroyed = true;
        }
        #endregion

        #region Saving/Loading
        #region Save
        public void Save(string fileName)
        {
            BinaryWriter file = new BinaryWriter(File.Open("data/objects/" + fileName + ".ent",FileMode.Create));
            file.Write(this.Color.R);
            file.Write(this.Color.G);
            file.Write(this.Color.B);
            file.Write(this.Color.A);
            file.Write(this.Enabled);
            file.Write(this.Rotation);
            file.Write(this.Scale.X);
            file.Write(this.Scale.Y);
            file.Write(this.textureName);
            file.Write(this.Visible);

            file.Write(this.PhysicsBody.Enabled);
            file.Write(this.PhysicsBody.FrictionCoefficient);
            file.Write(this.PhysicsBody.IgnoresGravity);
            file.Write(this.PhysicsBody.IsStatic);
            file.Write(this.PhysicsBody.KeepUpright);
            file.Write(this.PhysicsBody.LinearDragCoefficient);
            file.Write(this.PhysicsBody.Mass);
            file.Write(this.PhysicsBody.MomentOfInertia);
            file.Write(this.PhysicsBody.RotationalDragCoefficent);
            file.Write(this.PhysicsBody.Geometry.Count);

            foreach (Geom geom in this.PhysicsBody.Geometry)
            {
                file.Write(geom.CollisionResponseEnabled);
                file.Write(geom.LocalVertices.Count);
                foreach (Vector2 vert in geom.LocalVertices)
                {
                    file.Write(vert.X);
                    file.Write(vert.Y);
                }

                file.Write(geom.OffsetPosition.X);
                file.Write(geom.OffsetPosition.Y);
                file.Write(geom.RestitutionCoefficient);
                file.Write(geom.Rotation);
            }
            file.Close();
        }
        #endregion

        #region Load
        public static Entity Load(string fileName, KLib.Graphics.IMaterialService materials)
        {
            BinaryReader file = new BinaryReader(File.Open(fileName + ".ent", FileMode.Open));
            Entity newEntity = new Entity();

            newEntity.Color = new Color(file.ReadByte(), file.ReadByte(), file.ReadByte(), file.ReadByte());
            newEntity.Enabled = file.ReadBoolean();
            newEntity.Name = "Copy of " + fileName;
            newEntity.Position = Vector2.Zero;
            newEntity.Rotate(file.ReadSingle());
            newEntity.Scale = new Vector2(file.ReadSingle(), file.ReadSingle());
            newEntity.TextureName = file.ReadString();
            newEntity.Texture = materials.LoadAndGetTexture("test");
            newEntity.Visible = file.ReadBoolean();

            newEntity.PhysicsBody.Enabled = file.ReadBoolean();
            newEntity.PhysicsBody.FrictionCoefficient = file.ReadSingle();
            newEntity.PhysicsBody.IgnoresGravity = file.ReadBoolean();
            newEntity.PhysicsBody.IsStatic = file.ReadBoolean();
            newEntity.PhysicsBody.KeepUpright = file.ReadBoolean();
            newEntity.PhysicsBody.LinearDragCoefficient = file.ReadSingle();
            newEntity.PhysicsBody.Mass = file.ReadSingle();
            newEntity.PhysicsBody.MomentOfInertia = file.ReadSingle();
            newEntity.PhysicsBody.RotationalDragCoefficent = file.ReadSingle();

            int geomCount = file.ReadInt32();
            for (int i = 0; i < geomCount; i++)
            {
                Geom newGeom = new Geom(newEntity.PhysicsBody);
                newGeom.CollisionResponseEnabled = file.ReadBoolean();
                int vertCount = file.ReadInt32();
                List<Vector2> verts = new List<Vector2>();
                for (int k = 0; k < vertCount; k++)
                {
                    verts.Add(new Vector2(file.ReadSingle(), file.ReadSingle()));
                }
                newGeom.SetVertices(new Vertices(verts.ToArray()));
                newGeom.SetGeometryOffset(new Vector2(file.ReadSingle(), file.ReadSingle()));
                newGeom.RestitutionCoefficient = file.ReadSingle();
                newGeom.Rotate(file.ReadSingle());
                newEntity.PhysicsBody.Geometry.Add(newGeom);
            }
            file.Close();
            return newEntity;
        }
        #endregion

        #endregion
    }
}
